/* usb_linux.c -- simple usb interface library for linux
**
** Copyright 2006, Brian Swetland.  All rights reserved.
** See provided LICENSE file or http://frotz.net/LICENSE for details.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>

#include <ctype.h>

#include <linux/usbdevice_fs.h>

static int usb_fd = -1;
static int usb_ifc = -1;
static unsigned char usb_ep_in = 0;
static unsigned char usb_ep_out = 0;

static int find_usb_device(unsigned vendor, unsigned product)
{
    char busname[32], devname[32];
    DIR *busdir, *devdir;
    struct dirent *de;
    int fd;
    
    busdir = opendir("/proc/bus/usb");
    if(busdir == 0) return -1;

    while((de = readdir(busdir)) != 0) {
        if(!isdigit(de->d_name[0])) continue;
        
        sprintf(busname, "/proc/bus/usb/%s", de->d_name);
        devdir = opendir(busname);
        if(devdir == 0) continue;
        
        while((de = readdir(devdir))) {
            if(!isdigit(de->d_name[0])) continue;
            sprintf(devname, "%s/%s", busname, de->d_name);

            fd = open(devname, O_RDWR);
            if(fd < 0) {
                continue;
            } else {
                unsigned char devdesc[18];
                unsigned vid, pid;

                if(read(fd, devdesc, 18) != 18){
                    close(fd);
                    continue;
                }

                if((devdesc[0] == 18) && (devdesc[1] == 1)) {
                    vid = devdesc[8] | (devdesc[9] << 8);
                    pid = devdesc[10] | (devdesc[11] << 8);
                    if((vendor == vid) && (product == pid)){
                        closedir(devdir);
                        closedir(busdir);
                        return fd;
                    }
                }
                close(fd);
            }
        }
        closedir(devdir);
    }
    closedir(busdir);

    return -1;        
}

int usb_open(unsigned vendor, unsigned product, unsigned ifc,
             unsigned ep_in, unsigned ep_out)
{
    int n;

    usb_fd = find_usb_device(vendor, product);
    if(usb_fd < 0) {
        return -1;
    }

    n = (int) ifc;
    usb_ep_in = ep_in;
    usb_ep_out = ep_out;
    usb_ifc = n;

    n = ioctl(usb_fd, USBDEVFS_CLAIMINTERFACE, &n);
    if(n != 0) {
        usb_fd = -1;
        close(usb_fd);
        return -1;
    }

    return 0;
}

int usb_write(const void *_data, int len)
{
    unsigned char *data = (unsigned char*) _data;
    struct usbdevfs_bulktransfer bulk;
    int n;
    
    bulk.ep = usb_ep_out;
    bulk.len = len;
    bulk.data = data;
    bulk.timeout = 500 + 10 * len;
    
    n = ioctl(usb_fd, USBDEVFS_BULK, &bulk);

#if CHATTY
    if(n != len) {
        fprintf(stderr,"usb_write: %d %s\n",n,strerror(errno));
    }
#endif
    
    return n;
}

int usb_read(void *_data, int len)
{
    unsigned char *data = (unsigned char*) _data;
    struct usbdevfs_bulktransfer bulk;
    int n;

    bulk.ep = usb_ep_in;
    bulk.len = len;
    bulk.data = data;
    bulk.timeout = 500 + 10 * len;

    n = ioctl(usb_fd, USBDEVFS_BULK, &bulk);

#if CHATTY
    if(n != len) {
        fprintf(stderr,"usb_read: %d %s\n",n,strerror(errno));
    }
#endif
    return n;
}

int usb_close()
{
    ioctl(usb_fd, USBDEVFS_RELEASEINTERFACE, &usb_ifc);
    close(usb_fd);
    usb_fd = -1;
    return 0;
}

#if 0
int usb_reset()
{
    int n;
    ioctl(usb_fd, USBDEVFS_RELEASEINTERFACE, &usb_ifc);
    n = ioctl(usb_fd, USBDEVFS_RESET, 0);
    fprintf(stderr,"reset: %d %s\n", n, strerror(errno));
    close(usb_fd);
    usb_fd = -1;
    return 0;
}
#endif
